﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using NHibernate;
using System.Reflection;

namespace Apollo
{
    public partial class Application : HttpApplication
    {
        public const string ACTION_SAVE_NAME = "Save";
        public const string ACTION_CREATE_NAME = "Create";
        public const string ACTION_VIEW_NAME = "View";
        public const string ACTION_DELETE_NAME = "Delete";
        public const string ACTION_SAVE_VIEW_NAME = "SaveView";
        public const string ACTION_FILTER = "Filter";
        public const string ACTION_EXTRACOLUMNS = "ExtraColumns";

        public IList<IModule> Modules { get; set; }
        public IList<Action> Actions { get; set; }

        protected void CheckModuleConsistancy() {
            if (this.Modules == null || this.Modules.Count <= 0) {
                this.LoadModules();
                this.LoadActionHandlers();
            }
        }

        public void RegisterAction(string actionName) { 
            // check if action is already registered
            foreach (Action action in this.Actions)
                if (action.Name == actionName)
                    throw new ArgumentException("\"" + actionName + "\" already registered!");

            Action act = new Action(actionName);
            this.Actions.Add(act);
        }

        protected void LoadActionHandlers() {
            foreach (IModule module in this.Modules)
            {
                // find all handlers
                foreach (MethodInfo mi in module.GetType().GetMethods()) {
                    if (!mi.IsStatic)
                        continue;

                    foreach (object mi_attr in mi.GetCustomAttributes(typeof(ActionAttribute), true)) {
                        if (mi_attr is ActionAttribute) {
                            ActionAttribute attr = mi_attr as ActionAttribute;

                            Action action = this.Actions.SingleOrDefault<Action>(a => a.Name == attr.Name);
                            if (action != null) {
                                action.Handlers.Add(new ActionHandlerContainer()
                                {
                                    Handler = Delegate.CreateDelegate(typeof(ActionHandler), mi) as ActionHandler,
                                    HandlingType = attr.HandlingType,
                                    Priority = attr.Priority
                                });
                            }
                        }
                    }
                }
            }

            #warning Check Handling type and order by priority
        }

        protected void LoadModules() {
            foreach (LoadedAssembly assembly in assemblies)
                if (assembly.Module != null)
                    this.Modules.Add(assembly.Module);

            // call OnInit here to make sure that all modules were loaded ;)
            foreach (IModule module in this.Modules)
                module.OnInit(this);
        }

        public bool IsAllowed(string actionName, Entity entity, object obj) {
            this.CheckModuleConsistancy();

            bool isAllowed = true;

            // only modules are to be checked because the actions are allow always
            foreach (IModule module in this.Modules)
                isAllowed &= module.IsAllowed(actionName, entity, obj);

            return isAllowed;
        }

        protected IList<ActionSubscription> PrepareAction(string actionName, object targetedObject) {
            this.CheckModuleConsistancy();
            
            IList<ActionSubscription> resolvers = new List<ActionSubscription>();
            
            // from modules
            foreach (IModule module in this.Modules) {
                ActionSubscription subscription = module.PrepareAction(actionName, targetedObject);

                if(subscription == null)
                    continue; // this module does not handle this action

                resolvers.Add(subscription);
            }

            // from static modules
            Action action = this.Actions.FirstOrDefault(a => a.Name == actionName);
            if (action != null) 
                foreach (ActionHandlerContainer ahc in action.Handlers) 
                    resolvers.Add(new ActionSubscription(ahc));
            

            // order by priority ;)
            resolvers = resolvers.OrderBy(r => r.Priority).ToList();

            // if exists an active subscription, eliminate all other passive subscriptions
            if (resolvers.FirstOrDefault(r => r.HandlingType == ActionHandlingType.Active) != null)
                resolvers = resolvers.Where(r => r.HandlingType != ActionHandlingType.Passive).ToList();

            return resolvers;
        }

        public EditActionSubscription PrepareEditAction()
        {
            this.CheckModuleConsistancy();

            IList<EditActionSubscription> resolvers = new List<EditActionSubscription>();

            // from modules
            foreach (IModule module in this.Modules)
            {
                EditActionSubscription subscription = module.PrepareEditAction();

                if (subscription == null)
                    continue; // this module does not handle this action

                resolvers.Add(subscription);
            }


            // order by priority ;)
            resolvers = resolvers.OrderBy(r => r.Priority).ToList();

            // if exists an active subscription, eliminate all other passive subscriptions
            if (resolvers.FirstOrDefault(r => r.HandlingType == ActionHandlingType.Active) != null)
                resolvers = resolvers.Where(r => r.HandlingType != ActionHandlingType.Passive).ToList();

            if (resolvers.Count > 1)
                throw new Exception("Too many subscribers for this action!");
            else if (resolvers.Count == 0)
                throw new Exception("No subscriber for this action!");

            return resolvers[0];
        }

        protected ActionContext DoAction(string actionName, IList<ActionSubscription> subscribers, object targetedObject)
        {
            this.CheckModuleConsistancy();
            
            ActionContext currentContext = new ActionContext();

            if (subscribers.Count <= 0) {
                currentContext.AddMessage(new ActionMessage("No subscriber found for this action.", ActionMessageType.System));
                return currentContext;
            }

            // all resolvers will share the same transaction
            using (ITransaction tran = CurrentSession.BeginTransaction())
            {
                try
                {
                    // do the actual work
                    foreach (ActionSubscription subscriber in subscribers)
                    {
                        ActionContext context = null;

                        if (subscriber.CallerType == ActionSubscriptionCaller.Module)
                            context = CallDoActionOnModule(actionName, subscriber.Module, targetedObject);
                        else
                            context = CallDoActionOnStaticMethod(actionName, subscriber.Handler, targetedObject);

                        if (context == null)
                            continue;

                        currentContext.InnerContexts.Add(context);

                        // if the module that holds the active (CRUD) action fails,
                        // the hole transaction will fail and rollback
                        if (subscriber.HandlingType != ActionHandlingType.Passive
                            && context.State != ActionState.Succesful)
                        {
                            tran.Rollback();

                            // set state and messages and exit
                            currentContext.State = ActionState.Failed;
                            currentContext.AddRange(context.Messages);

                            // mark failed
                            CallEndActionOnContext(currentContext.InnerContexts, targetedObject, false);

                            return currentContext;
                        }
                        else if (context.State == ActionState.PartiallySuccessful 
                            || context.State == ActionState.PartiallySuccessful)
                        {
                            currentContext.State = ActionState.PartiallySuccessful;
                        }

                        // add messages to the context
                        currentContext.AddRange(context.Messages);
                    }

                    tran.Commit();

                    if (currentContext.State == ActionState.Unknown)
                    {
                        currentContext.State = ActionState.Succesful;

                        CallEndActionOnContext(currentContext.InnerContexts, targetedObject, true);
                    }
                }
                catch (Exception ex)
                {
                    tran.Rollback();

                    LogError(ex);

                    currentContext.AddMessage(new ActionMessage(ex.Message, ActionMessageType.System));

                    CallEndActionOnContext(currentContext.InnerContexts, targetedObject, false);
                }
            }

            return currentContext;
        }

        public ActionContext DoAction(string actionName, object obj) {
            try
            {
                return DoAction(actionName, this.PrepareAction(actionName, obj), obj);
            }
            catch (Exception ex)
            {
                LogError(ex);

                return new ActionContext(new ActionMessage(ex.Message, ActionMessageType.System));
            }
        }

        protected ActionContext CallDoActionOnModule(string actionName, IModule resolver, object targetedObject)
        {
            switch (actionName) { 
                case ACTION_SAVE_NAME:
                    return resolver.Save(targetedObject);
                case ACTION_DELETE_NAME:
                    return resolver.Delete(targetedObject);
                default:
                    return resolver.DoAction(actionName, targetedObject);
            }
        }

        protected ActionContext CallDoActionOnStaticMethod(string actionName, ActionHandler handler, object targetObject) {
            if (handler == null)
                return null;
            else
                return handler(targetObject);
        }

        protected void CallEndActionOnContext(IList<ActionContext> contexts, object obj, bool wasSuccessfull) {
            foreach (ActionContext context in contexts)
                CallEndActionOnContext(context, obj, wasSuccessfull);
        }

        protected void CallEndActionOnContext(ActionContext context, object obj, bool wasSuccessfull)
        {
            if(context != null)
                context.MarkActionEnd(obj, wasSuccessfull);
        }

        public ActionContext Save(object obj)
        {
            try
            {
                IList<ActionSubscription> subscribers = this.PrepareAction(ACTION_SAVE_NAME, obj);

                return DoAction(ACTION_SAVE_NAME, subscribers, obj);
            }
            catch (Exception ex) {
                LogError(ex);

                return new ActionContext(new ActionMessage(ex.Message, ActionMessageType.System));
            }
        }

        public ActionContext Delete(object obj)
        {
            try
            {
                IList<ActionSubscription> subscribers = this.PrepareAction(ACTION_SAVE_NAME, obj);

                return DoAction(ACTION_DELETE_NAME, subscribers, obj);
            }
            catch (Exception ex)
            {
                LogError(ex);

                return new ActionContext(new ActionMessage(ex.Message, ActionMessageType.System));
            }
        }
    }

}
